#![cfg_attr(not(test), no_std)]
#![feature(pointer_is_aligned_to)]

use core::{alloc::Layout, fmt::Debug, ptr::NonNull};

mod align;
pub mod err;
mod iter;
mod page_table_entry;
mod table;

pub use table::PageTableRef;

bitflags::bitflags! {
    /// Generic page table entry flags that indicate the corresponding mapped
    /// memory region permissions and attributes.
    #[repr(transparent)]
    #[derive(Clone, Copy, PartialEq, Eq)]
    pub struct AccessSetting: u8 {
        const Read = 1;
        const Write = 1 << 2;
        const Execute = 1 << 3;
    }
}

impl AccessSetting {
    pub fn readable(&self) -> bool {
        self.contains(AccessSetting::Read)
    }

    pub fn writable(&self) -> bool {
        self.contains(AccessSetting::Write)
    }

    pub fn executable(&self) -> bool {
        self.contains(AccessSetting::Execute)
    }
}

#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CacheSetting {
    Normal,
    Device,
    NonCache,
}

#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct MapConfig {
    pub vaddr: *const u8,
    pub paddr: usize,
    pub setting: PTESetting,
}

impl MapConfig {
    pub fn new(
        vaddr: *const u8,
        paddr: usize,
        privilege_access: AccessSetting,
        cache_setting: CacheSetting,
    ) -> Self {
        Self {
            vaddr,
            paddr,
            setting: PTESetting {
                cache_setting,
                privilege_access,
                user_access: AccessSetting::empty(),
                is_global: true,
            },
        }
    }

    pub fn set_user_access(&mut self, access: AccessSetting) -> Self {
        self.setting.user_access = access;
        *self
    }
}

#[repr(C)]
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct PTESetting {
    pub is_global: bool,
    pub privilege_access: AccessSetting,
    pub user_access: AccessSetting,
    pub cache_setting: CacheSetting,
}

impl Default for PTESetting {
    fn default() -> Self {
        Self {
            is_global: Default::default(),
            privilege_access: AccessSetting::empty(),
            user_access: AccessSetting::empty(),
            cache_setting: CacheSetting::Normal,
        }
    }
}

#[repr(C)]
#[derive(Default, Clone, PartialEq, Eq)]
pub struct PTEGeneric {
    pub paddr: usize,
    pub is_block: bool,
    pub is_valid: bool,
    pub setting: PTESetting,
}

impl PTEGeneric {
    pub(crate) fn new(paddr: usize, is_block: bool, setting: PTESetting) -> Self {
        Self {
            paddr,
            is_valid: true,
            is_block,
            setting,
        }
    }

    pub fn valid(&self) -> bool {
        self.is_valid
    }
}

impl Debug for PTEGeneric {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        write!(
            f,
            "PTE PA:{:#p} Block: {:>5} {:?}",
            self.paddr as *const u8, self.is_block, self.setting
        )
    }
}

pub trait PTEArch: Sync + Send + Clone + Copy + 'static {
    fn page_size() -> usize;
    fn level() -> usize;
    fn new_pte(config: PTEGeneric) -> usize;
    fn read_pte(pte: usize) -> PTEGeneric;
}

pub trait Access {
    /// offset = virtual address - physical address.
    fn va_offset(&self) -> usize;
    /// Alloc memory for a page table entry.
    ///
    /// # Safety
    ///
    /// should be deallocated by [`dealloc`].
    unsafe fn alloc(&mut self, layout: Layout) -> Option<NonNull<u8>>;
    /// dealloc memory for a page table entry.
    ///
    /// # Safety
    ///
    /// ptr must be allocated by [`alloc`].
    unsafe fn dealloc(&mut self, ptr: NonNull<u8>, layout: Layout);
}

pub struct PTEInfo {
    pub level: usize,
    pub vaddr: *const u8,
    pub pte: PTEGeneric,
}

impl Debug for PTESetting {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        macro_rules! field {
            ($i:expr,$e:ident,$name:expr) => {
                if $i.contains(AccessSetting::$e) {
                    f.write_str($name)?
                } else {
                    f.write_str("-")?
                }
            };
        }

        f.write_str("P")?;
        field!(self.privilege_access, Read, "R");
        field!(self.privilege_access, Write, "W");
        field!(self.privilege_access, Execute, "X");
        f.write_str(" | U")?;
        field!(self.user_access, Read, "R");
        field!(self.user_access, Write, "W");
        field!(self.user_access, Execute, "X");
        f.write_str(", ")?;
        f.write_fmt(format_args!("{:?}", self.cache_setting))?;
        f.write_str(")")
    }
}
